//cc -g http_monitor.c -I /usr/local/include/ -L /usr/local/lib/event2/ -levent -I /data/trunk/smap/ -L /data/trunk/smap/ -lsmap -lpthread
/*-
 * Copyright (c) 2012 zhuyan <nayuhz@gmail.com>
 * Http based monitor for pantao
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <unistd.h>

#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/http.h>
#include <event2/event_struct.h>
#include <queue.h>

#include <smap.h>

#include "http_monitor.h"
#include "log.h"


#define MAX_PROBE_VALUE_LEN 128
#define MAX_PROBE_NAME_LEN 32

static char index_page[] = 
/*
"<html>" \
"<head>" \
"<title>Help</title>" \
"</head>" \
"<body>" \
"<h1><strong>pantao http monitor</strong></h1>" \
"<p>usage: /command?args=...</p>" \
"<ul>" \
"<li>stat" \
"<ul>" \
"<li>/stat[?filter=&lt;all|mod_name&gt;[&amp;probe=&lt;probe_name&gt;]]</li>" \
"<li>get statistics of each mod, or filter=all, list all statistics</li>" \
"</ul>" \
"</li>" \
"</ul>" \
"<ul>" \
"<li>list" \
"<ul>" \
"<li>/list, list all mod name</li>" \
"</ul>" \
"</li>" \
"</ul>" \
"</body>" \
"</html>";
*/
"<!DOCTYPE html>"\
"<html>"\
"<head>"\
"<title>Pantao Monitor</title>" \
"<meta charset=\"utf-8\" />"\
"<style>"\
"body{font-size:13px;font-family:\"microsoft yahei\"}"\
"#nav{margin:10px 0;}"\
"em{color:#039;text-decoration:underline;cursor:pointer;}"\
"legend{border:1px solid #666;font-size:15px;padding:0 5px;background:#666;color:#fff;cursor:pointer;}"\
"li{border-bottom:1px dashed #ccc;padding:5px 0;}"\
"strong{width:200px;display:inline-block;cursor:pointer;}"\
"span{color:#666;}"\
"#timeVal{width:20px}"\
"</style>"\
"</head>"\
"<body>"\
"<div>interval: <input type=\'text\' width=\'20\' id=\'timeVal\' value=\'1\' />S&nbsp;&nbsp;<input type=\'button\' id=\'timeBtn\' value=\'set\' /></div>"\
"<div id=\"nav\"></div>"\
"<script type=\"text/javascript\">"\
"var time = 1,"\
"jsonUrl = \"/stat\";"\
"var xmlHttp,strHTML = \"\",h3 = \"\",h3Url = \"\",li = \"\",liUrl = \"\",only = true,nid = 0;"\
"function createXMLHttpRequest(){"\
"if(window.ActiveXObject){"\
"xmlHttp = new ActiveXObject(\"Microsoft.XMLHTTP\");"\
"}else if(window.XMLHttpRequest){"\
"xmlHttp = new XMLHttpRequest();"\
"}"\
"}"\
"function startRequest(url){"\
"createXMLHttpRequest();"\
"try{"\
"xmlHttp.onreadystatechange = handleStateChange;"\
"xmlHttp.open(\"GET\", url, true);"\
"xmlHttp.send(null);"\
"}catch(exception){"\
"alert(\"error\");"\
"}"\
"}"\
"function handleStateChange(){"\
"if(xmlHttp.readyState == 4){"\
"if (xmlHttp.status == 200 || xmlHttp.status == 0){"\
"eval(\"var res=\"+xmlHttp.responseText);"\
"changeData(res);"\
"}"\
"}"\
"}"\
"function changeData(data){"\
"if(only){"\
"var _div = document.getElementById(\"abc\");"\
"if(_div != null){"\
"document.body.removeChild(_div);"\
"strHTML = \"\";"\
"}"\
"var div = document.createElement(\'div\');"\
"if(data instanceof Array){"\
"var len = data.length;"\
"for(var i = 0;i<len;i++){"\
"jsonHTML(data[i]);"\
"nid++;"\
"}"\
"}else{"\
"jsonHTML(data);"\
"}"\
"div.innerHTML = strHTML;"\
"div.id = \"abc\";"\
"document.body.appendChild(div);"\
"only = false;"\
"}else{"\
"if(data instanceof Array){"\
"var len = data.length;"\
"for(var i = 0;i<len;i++){"\
"jsonEdit(data[i]);"\
"}"\
"}else{"\
"jsonEdit(data,\"\");"\
"}"\
"}"\
"var ds = document.getElementsByTagName(\"strong\");"\
"for(var a = 0;a<ds.length;a++){"\
"ds[a].onclick = function(){"\
"h3 = this.parentNode.parentNode.parentNode.firstChild.innerHTML;"\
"h3Url = \"?filter=\" + h3;"\
"li = this.innerHTML;"\
"liUrl = \"&probe=\" + li;"\
"only = true;"\
"init();"\
"};"\
"}"\
"var fi = document.getElementsByTagName(\"legend\");"\
"for(var b = 0;b<fi.length;b++){"\
"fi[b].onclick = editEm;"\
"}"\
"}"\
"var editEm = function(){"\
"h3 = this.innerHTML;"\
"h3Url = \"?filter=\" + h3;"\
"li = \"\";"\
"liUrl = \"\";"\
"only = true;"\
"init();"\
"};"\
"function jsonEdit(data,type){"\
"for(var i in data){"\
"if(data[i] instanceof Object){"\
"jsonEdit(data[i],i);"\
"}else{"\
"if(type == \"\"){"\
"var $sg = document.getElementsByTagName(\"strong\");"\
"for(var j=0;j<$sg.length;j++){"\
"(function(_j){"\
"var inn = String($sg[_j].innerHTML);"\
"var $span = $sg[_j].nextSibling;"\
"if(inn == i){"\
"if($span.innerHTML == data[i]){"\
"$span.style.color = \"#666\";"\
"}else{"\
"$span.style.color = \"#c00\";"\
"$span.innerHTML = data[i];"\
"}"\
"}"\
"})(j);"\
"}"\
"}else{"\
"var $sg = document.getElementsByTagName(\"strong\");"\
"for(var j=0;j<$sg.length;j++){"\
"(function(_j){"\
"var oun = String($sg[_j].parentNode.parentNode.parentNode.firstChild.innerHTML);"\
"var inn = String($sg[_j].innerHTML);"\
"var $span = $sg[_j].nextSibling;"\
"if(type == oun){"\
"if(inn == i){"\
"if($span.innerHTML == data[i]){"\
"$span.style.color = \"#666\";"\
"}else{"\
"$span.style.color = \"#c00\";"\
"$span.innerHTML = data[i];"\
"}"\
"}"\
"}"\
"})(j);"\
"}"\
"}"\
"}"\
"}"\
"}"\
"function jsonHTML(data){"\
"for(var i in data){"\
"if(data[i] instanceof Object){"\
"strHTML += \"<fieldset><legend>\" + i + \"</legend><ul rel=\'\"+nid+\"\'>\";"\
"jsonHTML(data[i]);"\
"strHTML += \"</ul></fieldset><br />\";"\
"}else{"\
"strHTML += \"<li><strong>\" + i + \"</strong>&nbsp;<span>\" + data[i] + \"</span></li>\";"\
"}"\
"}"\
"}"\
"function init(){"\
"startRequest(jsonUrl + h3Url + liUrl);"\
"var s = \"<em id=\'allNull\'>All</em>\";"\
"s += (h3 == \"\" ? \"\" : \"&nbsp;&nbsp;&gt;&gt;&nbsp;<em id=\'emem\'>\"+h3+\"</em>\");"\
"s += (li == \"\" ? \"\" : \"&nbsp;&nbsp;&gt;&gt;&nbsp;&nbsp;<i>\"+li+\"</i>\");"\
"document.getElementById(\"nav\").innerHTML = s;"\
"var $emem = document.getElementById(\"emem\");"\
"if($emem != null){"\
"$emem.onclick = editEm;"\
"}"\
"document.getElementById(\"allNull\").onclick = function(){"\
"h3 = \"\";li = \"\";h3Url = \"\";liUrl = \"\";"\
"only = true;"\
"init();"\
"};"\
"}"\
"document.getElementById(\"timeBtn\").onclick = function(){"\
"time = document.getElementById(\"timeVal\").value;"\
"runTime = window.clearInterval(runTime);"\
"run(time);"\
"};"\
"var runTime;"\
"function run(t){"\
"init();"\
"runTime = self.setInterval(function(){"\
"init();"\
"},t*1000);"\
"}"\
"run(time);"\
"</script>"\
"</body>"\
"</html>";

typedef char *M_FP(char *, int, void *);
struct M_ENTRY
{
	void *val;
	void *arg;
	int type;
};

typedef struct ip_node {
	STAILQ_ENTRY(ip_node)	entry;    /* Data buffer queue field */
	char	ip[32];
	int		flag;	/* reserved */
} IP_NODE;


STAILQ_HEAD(ip_list, ip_node);

static struct ip_list allowed_ip_list;
static struct SMAP* mod_map = NULL;

static int
check_ip(struct evhttp_request *req)
{
	struct evhttp_connection *evcon;
	char *addr;
	ev_uint16_t port;
	IP_NODE *inp;
	struct evbuffer *buf = NULL;
	
	evcon = evhttp_request_get_connection(req);
	evhttp_connection_get_peer(evcon, &addr, &port);
	//printf("allowed_ip size: %d, %s, %d\n", sizeof(allowed_ip), addr, port);

	STAILQ_FOREACH(inp, &allowed_ip_list, entry) {
		if (strcmp(addr, inp->ip) == 0)
			return (0);
	}	
	
	buf = evbuffer_new();
	if(!buf){
		puts("failed to create response buffer");
		return (-1);
	}
	evbuffer_add_printf(buf, "{\"request\":\"%s\", \"error_code\":\"%d\"," \
	"\"error\":\" IP: %s Permission denied\"}", evhttp_request_get_uri(req), 4001, addr);
	evhttp_send_reply(req, HTTP_NOTFOUND, "Permission denied", buf);
	evbuffer_free(buf);
	return (-1);
}


static int 
build_outstring(char *outbuf, size_t outbuf_len, struct M_ENTRY *e)
{
	switch (e->type) {
		case M_TYPE_INT:
			snprintf(outbuf, outbuf_len, "%jd", (intmax_t)(*((int *)(e->val))));
			break;
		case M_TYPE_LONG:
			snprintf(outbuf, outbuf_len, "%jd", (intmax_t)(*((long *)(e->val))));
			break;
		case M_TYPE_INT8:
			snprintf(outbuf, outbuf_len, "%jd", (intmax_t)(*((int8_t *)(e->val))));
			break;
		case M_TYPE_INT16:
			snprintf(outbuf, outbuf_len, "%jd", (intmax_t)(*((int16_t *)(e->val))));
			break;
		case M_TYPE_INT32:
			snprintf(outbuf, outbuf_len, "%jd", (intmax_t)(*((int32_t *)(e->val))));
			break;
		case M_TYPE_INT64:
			snprintf(outbuf, outbuf_len, "%jd", (intmax_t)(*((int64_t *)(e->val))));
			break;
		case M_TYPE_UINT:
			snprintf(outbuf, outbuf_len, "%jd", (uintmax_t)(*((unsigned int *)(e->val))));
			break;
		case M_TYPE_ULONG:
			snprintf(outbuf, outbuf_len, "%jd", (uintmax_t)(*((unsigned long *)(e->val))));
			break;
		case M_TYPE_UINT8:
			snprintf(outbuf, outbuf_len, "%ju", (uintmax_t)(*((uint8_t *)(e->val))));
			break;
		case M_TYPE_UINT16:
			snprintf(outbuf, outbuf_len, "%ju", (uintmax_t)(*((uint16_t *)(e->val))));
			break;
		case M_TYPE_UINT32:
			snprintf(outbuf, outbuf_len, "%ju", (uintmax_t)(*((uint32_t *)(e->val))));
			break;
		case M_TYPE_UINT64:
			snprintf(outbuf, outbuf_len, "%ju", (uintmax_t)(*((uint64_t *)(e->val))));
			break;
		case M_TYPE_FLOAT: 
			snprintf(outbuf, outbuf_len, "%Lf", (long double)(*((float *)(e->val))));
			break;
		case M_TYPE_DOUBLE:
			snprintf(outbuf, outbuf_len, "%Lf", (long double)(*((double *)(e->val))));
			break;
		case M_TYPE_STRING:
			snprintf(outbuf, outbuf_len, "%s", ((char *)(e->val)));
			break;
		case M_TYPE_FUNCTION:
			((M_FP*)(e->val))(outbuf, outbuf_len, e->arg);
			break;
		default :
			return (-1);
	}

	return (0);
}

static void
root_handler(struct evhttp_request *req, void *arg)
{
	int cred;
	struct evbuffer *buf = NULL;
	
	check_ip(req);
	
	buf = evbuffer_new();
	if(!buf){
		puts("failed to create response buffer");
		return;
	}
	
	evbuffer_add(buf, index_page, sizeof(index_page));
	evhttp_send_reply(req, HTTP_OK, "OK", buf);
	evbuffer_free(buf);
}

static void
generic_handler(struct evhttp_request *req, void *arg)
{
	struct evbuffer *buf = NULL;

	check_ip(req);
	
	buf = evbuffer_new();
	if(!buf){
		puts("failed to create response buffer");
		return;
	}
	
	evbuffer_add_printf(buf, "Requested: %s\n", evhttp_request_get_uri(req));
	evhttp_send_reply(req, HTTP_OK, "OK", buf);
	evbuffer_free(buf);
}


static void
stat_one(struct evbuffer *buf, struct PAIR *p, char *probe_filter)
{
	struct PAIR pair;
	struct PAIR *p1;
	char *mod_name, *probe_name;
	char keybuf[SMAP_MAX_KEY_LEN + 1];
	char outbuf[MAX_PROBE_VALUE_LEN];
	int i = 0;
	struct SMAP *probe_map;
	struct M_ENTRY *entry = NULL;

	mod_name = SMAP_GET_STR_KEY(p);
	evbuffer_add(buf, "{", 1);
	evbuffer_add_printf(buf, "\"%s\":", mod_name);
	
	probe_map = SMAP_GET_VALUE(p);
	
	if (!(probe_filter == NULL || *probe_filter =='\0')) {
		SMAP_SET_STR_KEY(&pair, probe_filter, strlen(probe_filter)+1);
		entry = smap_get(probe_map, &pair, 0);
	}
	
	evbuffer_add(buf, "{", 1);
	if (entry == NULL) {
		for (p1 = smap_get_first(probe_map, &pair, keybuf,
				SMAP_MAX_KEY_LEN + 1, KEYTYPE_ALL, 0);
			p1 != NULL;
			p1 = smap_get_next(probe_map, p1, keybuf,
				SMAP_MAX_KEY_LEN + 1, KEYTYPE_ALL, 0)) {
					if (i)
						evbuffer_add(buf, ",", 1);
					probe_name = SMAP_GET_STR_KEY(p1);
					entry = SMAP_GET_VALUE(p1);
					build_outstring(outbuf, MAX_PROBE_VALUE_LEN - 1, entry);
					evbuffer_add_printf(buf, "\"%s\":\"%s\"", probe_name, outbuf);
					i++;
		}
	} else {
		probe_name = SMAP_GET_STR_KEY(&pair);
		build_outstring(outbuf, MAX_PROBE_VALUE_LEN - 1, entry);
		evbuffer_add_printf(buf, "\"%s\":\"%s\"", probe_name, outbuf);
	}
	evbuffer_add(buf, "}", 1);	
	evbuffer_add(buf, "}", 1);
	return;
}

static void
stat_handler(struct evhttp_request *req, void *arg)
{
	struct evbuffer *buf = NULL;
	struct evhttp_uri *uri;
	struct evkeyvalq head;
	const char *filter;
	struct PAIR *p;
	struct PAIR pair;
	char keybuf[SMAP_MAX_KEY_LEN + 1];
	int i = 0;

	check_ip(req);
	
	buf = evbuffer_new();
	if(!buf){
		puts("failed to create response buffer");
		return;
	}

	uri = evhttp_uri_parse(evhttp_request_get_uri(req));
	evhttp_parse_query_str(evhttp_uri_get_query(uri), &head);
	filter = evhttp_find_header(&head, "filter");
	
	if (filter == NULL || strcmp(filter, "all") == 0) {
		evbuffer_add(buf, "[", 1);
		for (p = smap_get_first(mod_map, &pair, keybuf,
					SMAP_MAX_KEY_LEN + 1, KEYTYPE_ALL, 0);
			p != NULL;
			p = smap_get_next(mod_map, p, keybuf,
				SMAP_MAX_KEY_LEN + 1, KEYTYPE_ALL, 0)) {
					if (i)
						evbuffer_add(buf, ",", 1);
					stat_one(buf, p, NULL);
					i++;
		}
		evbuffer_add(buf, "]", 1);
	} else {
		SMAP_SET_STR_KEY(&pair, (char *)filter, strlen(filter) + 1);
		smap_get(mod_map, &pair, 0);
		filter = evhttp_find_header(&head, "probe");
		stat_one(buf, &pair, (char *)filter);
	}
	
	evhttp_send_reply(req, HTTP_OK, "OK", buf);

	evhttp_clear_headers(&head);
	evhttp_uri_free(uri);
	evbuffer_free(buf);
}

static void
list_handler(struct evhttp_request *req, void *arg)
{
	struct evbuffer *buf = NULL;
	struct evhttp_uri *uri;
	struct evkeyvalq head;
	struct PAIR *p;
	struct PAIR pair;
	char keybuf[SMAP_MAX_KEY_LEN + 1];
	char *mod_name;
	int i = 0;

	check_ip(req);
	
	buf = evbuffer_new();
	if(!buf){
		puts("failed to create response buffer");
		return;
	}


	//evbuffer_add_printf(buf, "stat: %s\n", evhttp_request_get_uri(req));
	uri = evhttp_uri_parse(evhttp_request_get_uri(req));
	//evbuffer_add_printf(buf, "query: %s\n", evhttp_uri_get_query (uri));
	evhttp_parse_query_str(evhttp_uri_get_query(uri), &head);
	
	evbuffer_add(buf, "[", 1);
	for (p = smap_get_first(mod_map, &pair, keybuf,
				SMAP_MAX_KEY_LEN + 1, KEYTYPE_ALL, 0);
		p != NULL;
		p = smap_get_next(mod_map, p, keybuf,
			SMAP_MAX_KEY_LEN + 1, KEYTYPE_ALL, 0)) {
				if (i)
					evbuffer_add(buf, ",", 1);
				mod_name = SMAP_GET_STR_KEY(p);
				evbuffer_add_printf(buf, "\"%s\"", mod_name);
				i++;
	}
	evbuffer_add(buf, "]", 1);

	
	evhttp_send_reply(req, HTTP_OK, "OK", buf);

	evhttp_clear_headers(&head);
	evhttp_uri_free(uri);
	evbuffer_free(buf);
}

int
set_probe(const char *mod_name, const char *item_name, int type, void *val, void *arg)
{
	struct PAIR pair;
	int mod_namelen;
	int rc;
	struct SMAP *probe_map;
	struct M_ENTRY e;

	rc = -1;
	if (mod_name == NULL || item_name == NULL || type <= 0 || val == NULL)
		return (rc);
	
	if (mod_map != NULL) {
		mod_namelen = strlen(mod_name) + 1;
		SMAP_SET_STR_KEY(&pair, mod_name, mod_namelen);
		probe_map = (struct SMAP *)smap_get(mod_map, &pair, 1);
		if (probe_map == NULL) {
			probe_map = smap_init(DEFAULT_INITIAL_CAPACITY,
				DEFAULT_LOAD_FACTOR, 128, DEFAULT_ENTRY_POOL_SIZE, 1);
			SMAP_SET_STR_PAIR(&pair, mod_name, mod_namelen, probe_map, sizeof(struct SMAP));
			rc = smap_put(mod_map, &pair, 0);
		}
		e.val = val;
		e.arg = arg;
		e.type = type;
		SMAP_SET_STR_PAIR(&pair, item_name, strlen(item_name) + 1, &e, sizeof(struct M_ENTRY));
		rc = smap_put(probe_map, &pair, 1);
	}
	return (rc);
}

static char *
strip(char* s) /* return rstrip( lstrip(s) ); ... pulled apart here */
{
    if( *s == 0 )
        return s;
    else {
        while( isspace( *s ) ) ++s; /* lstrip ... */
        if( *s == 0 ) return s;
        else {
            char* back = s + strlen( s ) - 1; /* now .... rstrip ... */
            while( isspace( *back ) ) --back;
            *(back+1) = 0;
            return s;
        }
    }
}

static int
build_ip_list(char *filename, struct ip_list *l, int flag)
{
	FILE *fp;
	char buf[128];
	char *ip;
	IP_NODE *inp;
	
	fp = fopen(filename, "rt");

	if (fp == NULL)
		return (-1);
	
	while(fgets(buf, 128, fp) != NULL) {
		ip = strip(buf);
		inp = (IP_NODE *)malloc(sizeof(IP_NODE));
		if (inp == NULL)
			return (-1);
		strlcpy(inp->ip, ip, 32);
		inp->flag = flag;
		STAILQ_INSERT_TAIL(l, inp, entry);
    }
	
	return (0);
}

typedef struct monitor_args
{
	char *allowed_ip_filename;
	char *bindip;
	uint16_t port;
} MONITOR_ARGS;

static MONITOR_ARGS g_margs;

static void *
monitor_start(void *args)
{
	struct event_base *base;
	struct evhttp *httpd;

	int ret;
	MONITOR_ARGS *margs = args;
	
	STAILQ_INIT(&allowed_ip_list);
	ret = build_ip_list(margs->allowed_ip_filename, &allowed_ip_list, 1);
	if (ret < 0) {
		WLOG_ERR("ERROR: CAN NOT build allowed IP list, monitor is disabled!");
		PRINT("ERROR: CAN NOT build allowed IP list, monitor is disabled!");
		return (NULL);
	}
	
	mod_map = smap_init(DEFAULT_INITIAL_CAPACITY,
		DEFAULT_LOAD_FACTOR, 128, DEFAULT_ENTRY_POOL_SIZE, 1);

	base = event_base_new();

	httpd = evhttp_new(base);
	if (!httpd) {
		return (NULL);
	}
	evhttp_bind_socket(httpd, margs->bindip, margs->port);
	evhttp_set_cb(httpd, "/", root_handler, NULL);
	evhttp_set_cb(httpd, "/stat", stat_handler, NULL);
	evhttp_set_cb(httpd, "/list", list_handler, NULL);
	evhttp_set_gencb(httpd, root_handler, NULL);

	event_base_dispatch(base);

	evhttp_free(httpd);
	return (NULL);
}

int
monitor_init(char *allowed_ip_filename, char *bindip, uint16_t port)
{
	pthread_t tid;

	g_margs.allowed_ip_filename = allowed_ip_filename;
	g_margs.bindip = bindip;
	g_margs.port = port;
	pthread_create(&tid, NULL, monitor_start, &g_margs);
	
	return (0);
}

/*
int main(int argc, wchar_t* argv[])
{

	monitor_init("allowed_ip_list", "0.0.0.0", 8080);
	set_probe("test.so", "Hello World!", M_TYPE_INT, &test_int);
	set_probe("test.so", "Hello Earth!", M_TYPE_INT, &test_int);
	set_probe("main", "Good Night", M_TYPE_INT, &test_int);
	set_probe("main", "Good Noon", M_TYPE_INT, &test_int);
	set_probe("main", "Test Func", M_TYPE_FUNCTION, test_func);
	
	usleep(1000*1000*1000);
	return 0;
}
*/

